﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LaughingDog.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using LaughingDog;
using System.Reflection;
using LaughingDog.Managers;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Globalization;
using System.IO;
using LaughingDog.Input;

namespace LaughingDog.GameObjects
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="otherGameObject"></param>
    //public delegate void OnBoundingBoxCollision(GameObject otherGameObject);

    /// <summary>
    /// 
    /// </summary>
    /// <param name="otherGameObject"></param>
    //public delegate void OnPixelPerfectCollision(GameObject otherGameObject);

    /// <summary>
    /// 
    /// </summary>
    public delegate void OnMouseHoverEventHandler();

    /// <summary>
    /// 
    /// </summary>
    /// <param name="button"></param>
    public delegate void OnMousePressedEventHandler(MouseButtons button);    

    /// <summary>
    /// 
    /// </summary>
    /// <param name="button"></param>
    public delegate void OnMouseJustPressedEventHandler(MouseButtons button);

    /// <summary>
    /// 
    /// </summary>
    /// <param name="button"></param>
    public delegate void OnMouseJustReleasedEventHandler(MouseButtons button);

    /// <summary>
    /// Base class for all entities in LaughingDog. Every GameObject has a Transform Component by default.
    /// </summary>
    public class GameObject : LaughingDog.Object
    {        
        private List<Component> mComponents = new List<Component>();
        private List<IDrawableComponent> mDrawableComponents = new List<IDrawableComponent>();
        
        private Transform mTransform = null;
        private RigidBody mRigidBody = null;
        private List<ConstantForce> mConstantForces = new List<ConstantForce>();
        private Collider mCollider = null;         
        private Renderer mRenderer = null;
        private List<Animation> mAnimations = new List<Animation>();
        private List<AudioSource> mAudioSources = new List<AudioSource>();
        private Text mText = null;
        private Camera mCamera = null;
        private List<Script> mScripts = new List<Script>();

        public OnMouseHoverEventHandler OnMouseHovering;
        public OnMousePressedEventHandler OnMousePressed;
        public OnMouseJustPressedEventHandler OnMouseJustPressed;
        public OnMouseJustReleasedEventHandler OnMouseJustReleased;

        //public OnBoundingBoxCollision OnBoundingBoxCollision;
        //public OnPixelPerfectCollision OnPixelPerfectCollision;

        /// <summary>
        /// Sets or gets the parent of this GameObject.
        /// </summary>
        public GameObject Parent { get { return Transform.Parent.GameObject; } set { Transform.Parent = value.Transform; } }

        /// <summary>
        /// A list of all Components attached to the GameObject.
        /// </summary>
        public List<Component> Components { get { return mComponents; } }

        /// <summary>
        /// A list of all IDrawableComponents attached to the GameObject.
        /// </summary>
        public List<IDrawableComponent> DrawableComponents { get { return mDrawableComponents; } }

        /// <summary>
        /// The Transform attached to this GameObject.        
        /// </summary>        
        public Transform Transform { get { return mTransform; } }

        /// <summary>
        /// The RigidBody attached to this GameObject. Null if there isn't one attached.
        /// </summary>
        public RigidBody RigidBody { get { return mRigidBody; } }

        /// <summary>
        /// A list of all ConstantForces attached to the GameObject.
        /// </summary>
        public List<ConstantForce> ConstantForces { get { return GetComponents<ConstantForce>(); } }

        /// <summary>
        /// The Collider attached to this GameObject. Null if there isn't one attached.
        /// </summary>
        public Collider Collider { get { return mCollider; } }

        /// <summary>
        /// The Camera attached to this GameObject. Null if there isn't one attached.
        /// </summary>
        public Camera Camera { get { return mCamera; } }

        /// <summary>
        /// The Renderer attached to this GameObject. Null if there isn't one attached.
        /// </summary>
        public Renderer Renderer { get { return mRenderer; } }

        /// <summary>
        /// A list of all Animations attached to the GameObject.
        /// </summary>
        public List<Animation> Animations { get { return GetComponents<Animation>(); } }

        /// <summary>
        /// A list of all AudioSources attached to the GameObject.
        /// </summary>
        public List<AudioSource> Audio { get { return GetComponents<AudioSource>(); } }

        /// <summary>
        /// The Text component attached to this GameObject. Null if there isn't one attached.
        /// </summary>
        public Text Text { get { return mText; } }

        /// <summary>
        /// A list of all Scripts attached to the GameObject.
        /// </summary>
        public List<Script> Scripts { get { return GetComponents<Script>(); } }

        /// <summary>
        /// Creates a new instance of a GameObject and adds it to the engine.
        /// </summary>
        public GameObject()
        {
            AddComponent<Transform>();            

            GameObjectManager.Instance.AddGameObject(this);
        }

        /// <summary>
        /// Updates this GameObject.
        /// </summary>        
        public virtual void Update()
        {
            if (Active)
            {
                foreach (Component component in new List<Component>(mComponents))
                {
                    if (component is Behaviour)
                    {
                        Behaviour behaviour = (Behaviour)component;

                        if (behaviour.Enabled)
                            behaviour.Update();                        
                    }
                    else
                        component.Update();
                }
            }
        }

        /// <summary>
        /// Draws all visible IDrawableComponents attached to this GameObject.
        /// </summary>
        /// <param name="spriteBatch"></param>
        public virtual void Draw(SpriteBatch spriteBatch)
        {
            if (Active)
            {
                foreach (Component component in new List<Component>(mComponents))
                {
                    if (component is IDrawableComponent)
                    {
                        IDrawableComponent drawable = (IDrawableComponent)component;

                        if (drawable.Visible)
                            drawable.Draw(spriteBatch);
                    }
                }
            }          
        }

        /// <summary>
        /// Adds a Component to the GameObject named 'name'.
        /// </summary>
        /// <typeparam name="T">The type of Component to add.</typeparam>
        /// <returns>The added component.</returns>
        public T AddComponent<T>() where T : Component
        {
            return AddComponent<T>("");
        }

        /// <summary>
        /// Adds a Component to the GameObject.
        /// </summary>
        /// <typeparam name="T">The type of Component to add.</typeparam>
        /// <param name="name">The name of the Component.</param>
        /// <returns>The added component.</returns>
        public T AddComponent<T>(String name) where T : Component
        {
            object obj = Activator.CreateInstance(typeof(T), this);

            mComponents.Add((T)obj);
            

            if (!name.Equals(""))
            {
                Component com = (Component)obj;
                com.Name = name;
            }

            if (obj is IDrawableComponent)
                mDrawableComponents.Add((IDrawableComponent)obj);

            if (obj is Transform)
                mTransform = (Transform)obj;

            if (obj is RigidBody)            
                mRigidBody = (RigidBody)obj;                            

            if (obj is Collider)
                mCollider = (Collider)obj;

            if (obj is Renderer)
                mRenderer = (Renderer)obj;

            if (obj is Camera)
            {
                mCamera = (Camera)obj;
                if (Camera.ActiveCamera == null)
                    Camera.ActiveCamera = mCamera;
            }

            if (obj is Text)
                mText = (Text)obj;

            return (T)obj;
        }

        /// <summary>
        /// Adds a precompiled Script component to this GameObject. Used for Xbox360 compatability.
        /// </summary>
        /// <param name="dllPath">The path to the compiled dll.</param>        
        /// <returns>The Script component.</returns>
        public Script AddScriptFromDLL(String dllPath)
        {
            String scriptTypeName = Path.GetFileNameWithoutExtension(dllPath);

            Assembly scriptDll = Assembly.LoadFrom(dllPath);
            Type type = scriptDll.GetType(scriptTypeName);
            Script compiledScript = Activator.CreateInstance(type, this) as Script;

            if (compiledScript != null)
            {                
                //compiledScript.SourceDLL = dllPath;
                //compiledScript.ScriptTypeName = scriptTypeName;

                //mScripts.Add(compiledScript);
                mComponents.Add(compiledScript);

                //compiledScript.Awake();
            }

            return compiledScript;
        }

        /// <summary>
        /// Compiles a Script component and adds it to this GameObject.
        /// </summary>
        /// <param name="scriptPath">The path to the script source file.</param>        
        /// <returns>The Script component.</returns>
        public Script AddScriptFromFile(String scriptPath)
        {            
            Script compiledScript = Script.Compile(scriptPath, this);

            if (compiledScript != null)
            {
                //mOriginalScripts.Add(scriptPath, compiledScript);

                //mScripts.Add(compiledScript);

                mComponents.Add(compiledScript);
                //mScriptWatchers.Add(createScriptWatcher(compiledScript), compiledScript);

                //compiledScript.Awake();
            } 

            return compiledScript;
        }

        /// <summary>
        /// Returns the first found Component of the type specified. Null if none have been attached.
        /// </summary>
        /// <typeparam name="T">The type of Component to get.</typeparam>
        /// <returns>The retrieved Component.</returns>
        public T GetComponent<T>() where T : Component
        {
            foreach (Component component in mComponents)
                if (component is T)
                    return (T)component;

            return null;
        }

        /// <summary>
        /// Returns the first found Component of the type specified with the passed in name. Null if none have been attached.
        /// </summary>        
        /// <typeparam name="T">The type of Component to get.</typeparam>
        /// <param name="name">The name of the Component to get.</param>
        /// <returns>The retrieved Component.</returns>
        public T GetComponent<T>(String name) where T : Component
        {
            foreach (Component component in mComponents)
                if (component is T)
                    if(component.Name.Equals(name))
                        return (T)component;

            return null;
        }

        /// <summary>
        /// Gets a list of all attached Components of the specified type.
        /// </summary>
        /// <typeparam name="T">The type of Components to retrieve.</typeparam>
        /// <returns>The list of Components.</returns>
        public List<T> GetComponents<T>() where T : Component
        {
            List<T> foundComponents = new List<T>();

            foreach (Component component in mComponents)
                if (component is T)
                    foundComponents.Add((T)component);

            return foundComponents;
        }

        /// <summary>
        /// Gets a list of all attached Components of the specified type.
        /// </summary>
        /// <typeparam name="T">The type of Components to retrieve.</typeparam>
        /// <param name="name">The name of the Components to retrieve.</param>
        /// <returns>The list of Components.</returns>
        public List<T> GetComponents<T>(String name) where T : Component
        {
            List<T> foundComponents = new List<T>();

            foreach (Component component in mComponents)
                if (component is T)
                    if (component.Name.Equals(name))
                        foundComponents.Add((T)component);

            return foundComponents;
        }

        /// <summary>
        /// Checks if the GameObject has a specific type of Component.
        /// </summary>
        /// <typeparam name="T">The Type of the component to check for.</typeparam>
        /// <returns>True if a component of type T was found, false if not.</returns>
        public Boolean HasComponent<T>() where T : Component
        {
            return GetComponent<T>() != null;
        }

        /// <summary>
        /// Checks if the GameObject has a specific type of Component with a specific name.
        /// </summary>
        /// <typeparam name="T">The Type of the component to check for.</typeparam>
        /// <param name="name">The Name of the component to check for.</param>
        /// <returns>True if a component of type T with a name of 'name' was found, false if not.</returns>
        public Boolean HasComponent<T>(String name) where T : Component
        {
            return GetComponent<T>(name) != null;
        }

        /// <summary>
        /// Removes a Component from this GameObject.
        /// </summary>
        /// <param name="component">The Component to remove.</param>
        public void RemoveComponent(Component component)
        {
            try
            {
                if (component is Transform)
                {
                }
                else
                {
                    if (component is IDrawableComponent)
                        mDrawableComponents.Remove((IDrawableComponent)component);

                    if (component is RigidBody)
                    {
                        PhysicsManager.Instance.Simulator.Remove(mRigidBody.Body);
                        mRigidBody = null;
                    }

                    if (component is Collider)
                    {
                        PhysicsManager.Instance.Simulator.Remove(mCollider.Geom);
                        mCollider = null;
                    }                  

                    if (component is Renderer)
                        mRenderer = null;
         
                    if (component is Text)
                        mText = null;     

                    mComponents.Remove(component);
                }
            }
            catch (NullReferenceException)
            {
            }

        }

        /// <summary>
        /// Is this GameObject tagged with the specified tag?
        /// </summary>
        /// <param name="tag">The tag to check.</param>
        /// <returns>True if it is tagged, false if not.</returns>
        public Boolean CompareTag(String tag)
        {
            return Tag.Equals(tag);
        }

        /// <summary>
        /// Calls the method named methodName on all attached Scripts.
        /// </summary>
        /// <param name="methodName">The name of the method.</param>
        /// <param name="args">Any parameters that are required by the method.</param>
        public void SendMessage(String methodName, params object[] args)
        {
            foreach (Script script in new List<Script>(Scripts))
            {
                Type objectType = script.GetType();

                foreach (MethodInfo methodInfo in objectType.GetMethods(BindingFlags.Public))
                {
                    if (methodInfo.Name.Equals(methodName))                                            
                        methodInfo.Invoke(script, args);                    
                }
            }            
        }

        /// <summary>
        /// Calls the method named methodName on all attached Scripts of this GameObject and all of its children.
        /// </summary>
        /// <param name="methodName">The name of the method.</param>
        /// <param name="args">Any parameters that are required by the method.</param>
        public void BroadcastMessage(String methodName, params object[] args)
        {
            SendMessage(methodName, args);

            foreach (Transform transform in new List<Transform>(Transform.Children))
                transform.GameObject.SendMessage(methodName, args);
        }

        /// <summary>
        /// Returns one active GameObject tagged with tag. Returns null if none are found.
        /// </summary>
        /// <param name="tag">The tag to search for.</param>
        /// <returns>The found GameObject.</returns>
        public static GameObject FindWithTag(String tag)
        {
            foreach (GameObject gameObject in new List<GameObject>(GameObjectManager.Instance.Objects))
            {
                if (gameObject.Tag.Equals(tag) && gameObject.Active)
                    return gameObject;
            }

            return null;
        }

        /// <summary>
        /// Returns a list of active GameObjects tagged tag.
        /// </summary>
        /// <param name="tag">The tag to search for.</param>
        /// <returns>The list of found GameObjects.</returns>
        public static List<GameObject> FindGameObjectsWithTag(String tag)
        {
            List<GameObject> taggedObjects = new List<GameObject>();

            foreach (GameObject gameObject in new List<GameObject>(GameObjectManager.Instance.Objects))
            {
                if (gameObject.Tag.Equals(tag) && gameObject.Active)
                    taggedObjects.Add(gameObject);
            }

            return taggedObjects;
        }

        /// <summary>
        /// Returns a GameObject named name. Null if one wasn't found.
        /// </summary>
        /// <param name="name">The name of the GameObject to search for.</param>
        /// <returns>The found GameObject.</returns>
        public static GameObject Find(String name)
        {
            foreach (GameObject gameObject in new List<GameObject>(GameObjectManager.Instance.Objects))
            {
                if (gameObject.Name.Equals(name))
                    return gameObject;
            }

            return null;
        }

        /// <summary>
        /// Called automatically when the mouse is hovering over the Collider (if any) attached to this GameObject.
        /// </summary>
        public virtual void MouseHovering()
        {

        }

        /// <summary>
        /// Called automatically when the mouse pressed down over the Collider (if any) attached to this GameObject.
        /// </summary>
        /// <param name="button">The mouse button that was pressed.</param>
        public virtual void MouseJustPressed(MouseButtons button)
        {

        }

        /// <summary>
        /// Called automatically while the mouse is held down on the Collider (if any) attached to this GameObject.
        /// </summary>
        /// <param name="button">The mouse button that was pressed.</param>
        public virtual void MousePressed(MouseButtons button)
        {

        }  

        /// <summary>
        /// Called automatically when the mouse is first released when hovering over the Collider (if any) attached to this GameObject.
        /// </summary>
        /// <param name="button">The mouse button that was released.</param>
        public virtual void MouseJustReleased(MouseButtons button)
        {

        }

        ///// <summary>
        ///// Called automatically when this GameObjects BoundingBox collides with another GameObjects BoundingBox.
        ///// </summary>
        ///// <param name="otherGameObject">The GameObject that this one collided with.</param>
        //public virtual void BoundingBoxCollision(GameObject otherGameObject)
        //{
        //}

        ///// <summary>
        ///// Called automatically when this GameObjects Renderer collides with another GameObjects Renderer.
        ///// </summary>
        ///// <param name="otherGameObject">The GameObject that this one collided with.</param>
        //public virtual void PixelPerfectCollision(GameObject otherGameObject)
        //{
        //}

        ///// <summary>
        ///// Called automatically when this GameObjects Renderer collides with another GameObjects Renderer. Accounting for linear translations.
        ///// </summary>
        ///// <param name="otherGameObject">The GameObject that this one collided with.</param>
        //public virtual void PixelPerfectCollisionTranslated(GameObject otherGameObject)
        //{
        //}   
    }
}
